#ifndef SIMPLE_IO_WRITE_H
#define SIMPLE_IO_WRITE_H

#include <cstddef> // std::size_t std::byte
#include <type_traits> // std::enable_if_t
#include <variant> // std::variant std::visit
#include <string> // std::string
#include <utility> // std::forward

#include "simple/support/meta/pend.hpp" // support::meta::append_t

#include "handle.h" // operation interface
#include "error.h" // error
#include "standard.h" // standard_error_t standard_output_t
#include "byte_view.hpp" // byte_view
#include "read_write_common.h" // not_seekable socket_not_connected
#include "write_resize_common.h" // size_limit_reached not_write_handle
#include "write_socket_common.h" // network_unreachable network_interface_down

namespace simple::io
{
	namespace meta = support::meta;

	struct no_space_to_write : error {};
	struct bad_write_to_pipe : error {};
	struct system_out_of_resources_to_write : error {};
	struct bad_write_to_device : error {};
	struct write_access_denied : error {};

	using common_write_result = std::variant<const std::byte*,
		size_limit_reached,
		no_space_to_write,
		system_out_of_resources_to_write,
		bad_write_to_device,
		error
	>;

	using write_result = meta::append_t<common_write_result, bad_write_to_pipe>;
	using write_offset_result = meta::append_t<common_write_result, not_seekable>;

	// TODO: these apply to sockets which should have their own interface type, distinct from files
	// the underlying unique_handle is the same though (close still works afaik) on that level these are included
	using net_write_result = meta::append_t<write_result,
		socket_not_connected,
		write_access_denied,
		network_interface_down,
		network_unreachable
    >;

	using ex_write_result = meta::append_t<net_write_result, not_write_handle>;
	using ex_write_offset_result = meta::append_t<write_offset_result, not_write_handle>;

	[[nodiscard]]
	ex_write_result write(const unique_handle&, byte_view) noexcept;

	[[nodiscard]]
	ex_write_offset_result write(const unique_handle&, byte_view, std::size_t offset) noexcept;

	[[nodiscard]]
	ex_write_result write(standard_output_t, byte_view) noexcept;

	[[nodiscard]]
	ex_write_result write(standard_error_t, byte_view) noexcept;

	[[nodiscard]]
	ex_write_offset_result write(standard_output_t, byte_view, std::size_t offset) noexcept;

	[[nodiscard]]
	ex_write_offset_result write(standard_error_t, byte_view, std::size_t offset) noexcept;

	template <operation allowed,
		std::enable_if_t<allowed && operation::write>* = nullptr>
	write_result write(const interface<allowed>& i, byte_view bv) noexcept
	// TODO: return to<write_result>(write(i.handle, bv));
	{ return std::visit([](auto&& x){ return write_result(x); }, write(i.handle, bv)); }

	template <operation allowed,
		std::enable_if_t<allowed && operation::write>* = nullptr>
	write_offset_result write(const interface<allowed>& i, byte_view bv, std::size_t off) noexcept
	// TODO: return to<write_offset_result>(write(i.handle, bv, off));
	{ return std::visit([](auto&& x){ return write_offset_result(x); }, write(i.handle, bv, off)); }

	// TODO: overload that would automatically apply as_byte_view to second argument, useful for printing arguments in case of errors

	constexpr struct write_f_t
	{
		template <typename... Args>
		[[nodiscard]] decltype(auto) operator()(Args&& ... args) const noexcept
		{ return simple::io::write(std::forward<Args>(args)...); }
	} write_f;

	[[nodiscard]] inline std::string get_message(const write_f_t&)
	{ return "simple::io::write"; }

} // namespace simple::io

#endif /* end of include guard */
